#include "EthSeriesCoinApi.hpp"
#include "../utils/KeyCrypterScrypt.h"
#include "../common/BtSeed.hpp"
#include "../utils/Common.h"
#include "EthCreateSignTransactionParams.hpp"
#include "libdevcrypto/Common.h"
#include "Transaction.h"

string EthSeriesCoinApi::getAddress(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    const hd_private m(dc, hd_private::mainnet);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret ecs = tmp.secret();


    //兼容android

    dev::bytesRef bytesRef(ecs.begin(), ecs.size());
    uint8_t sign = (uint8_t)ecs[0];
    string strAddress;

    if(sign >= 0x80) {
        std::array<uint8_t, 33> arr;
        arr[0] = 0x00;
        for(int j = 0; j < 32; j++) {
            arr[1 + j] = ecs[j];
        }

        dev::bytesConstRef bytesconstRef(arr.begin(), arr.size());
        dev::sha3(bytesconstRef, bytesRef);

        dev::Secret secret(bytesRef);

        dev::KeyPair keyPair(secret);
        dev::Address address = keyPair.address();
        strAddress = "0x" + address.hex();

        return strAddress;
    }

    dev::bytesConstRef bytesconstRef(ecs.begin(), ecs.size());
    dev::sha3(bytesconstRef, bytesRef);

    dev::Secret secret(bytesRef);

    dev::KeyPair keyPair(secret);
    dev::Address address = keyPair.address();
    strAddress = "0x" + address.hex();

    return strAddress;
}

bool EthSeriesCoinApi::createWalltByPassword(vector<string> words, string password, vector<ChildNumber> path, NetParams netparams, BtWallet& bw)
{
    const auto seed = decode_mnemonic(words);
    data_chunk dc;

    dc = to_chunk(seed);

    uint8_t salt[8] = {0};
    KeyCrypterScrypt::randomSalt(salt, 8);

    KeyCrypterScrypt kcs;

    uint32_t N = netparams.getN();
    uint32_t R = netparams.getR();
    uint32_t P = netparams.getP();

    if(netparams.getApiVersion() == 0x01) {
        N =32768;
        R = 8;
        P = 1;
    }

    vector<uint8_t> vecIv;
    bool res = kcs.deriveKey(password, salt, 8, N, R, P, vecIv);
    if(!res) {
        return false;
    }


    uint8_t encryptBytes[256] = {0};
    int encryptLen = kcs.encrypt((uint8_t*)&seed[0], seed.size(), encryptBytes, vecIv);
    if(encryptLen <= 0) {        return false;
    }

    //IV iv;
    //kcs.getIV(iv);

    vector<uint8_t> vSeed(encryptBytes, encryptBytes + encryptLen);

    EncryptedData encSeedData;
    encSeedData.setIV(vecIv);
    encSeedData.setEncryptedBytes(vSeed);

    vector<uint8_t> vMnemonic;
    getMnemonicAsBytes(words, vMnemonic);

    encryptLen = kcs.encrypt((uint8_t*)&vMnemonic[0], vMnemonic.size(), encryptBytes, vecIv);
    if(encryptLen <= 0) {
        return false;
    }

    vector<uint8_t> vMn(encryptBytes, encryptBytes + encryptLen);

    EncryptedData encMnData;
    encMnData.setIV(vecIv);
    encMnData.setEncryptedBytes(vMn);

    string hashPwd;
    getSHA256(password, hashPwd);

    long creationTimeSeconds = time(NULL);

    vector<uint8_t> vSalt(&salt[0], &salt[8]);

    BtSeed* btSeed = new BtSeed(dc, words, encMnData, encSeedData, creationTimeSeconds, hashPwd, vSalt);
    bw.setBtSeed(btSeed);
    bw.setSymbol(netparams.getSymbol());

    string address;

    if(netparams.getApiVersion() == 0x01) {
        address = getAddress(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    else
    {
        address = getAddressV2(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    bw.setAddress(address);

    return true;
}

string EthSeriesCoinApi::getPrivate(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    const hd_private m(dc, hd_private::mainnet);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret ecs = tmp.secret();
    //兼容android

    dev::bytesRef bytesRef(ecs.begin(), ecs.size());
    uint8_t sign = (uint8_t)ecs[0];
    string priKey;

    if(sign >= 0x80) {
        std::array<uint8_t, 33> arr;
        arr[0] = 0x00;
        for(int j = 0; j < 32; j++) {
            arr[1 + j] = ecs[j];
        }

        dev::bytesConstRef bytesconstRef(arr.begin(), arr.size());
        dev::sha3(bytesconstRef, bytesRef);
        dev::Secret secret(bytesRef);

        priKey = secret.hex();
        return priKey;
    }

    dev::bytesConstRef bytesconstRef(ecs.begin(), ecs.size());
    dev::sha3(bytesconstRef, bytesRef);

    dev::Secret secret(bytesRef);

    priKey = secret.hex();

    return priKey;
}

string EthSeriesCoinApi::getAddressV2(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    const hd_private m(dc, hd_private::mainnet);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret ecs = tmp.secret();

    dev::bytesRef bytesRef(ecs.begin(), ecs.size());

    string strAddress;
    dev::Secret secret(bytesRef);

    dev::KeyPair keyPair(secret);
    dev::Address address = keyPair.address();
    strAddress = "0x" + address.hex();

    return strAddress;
}
string EthSeriesCoinApi::getPrivateV2(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    const hd_private m(dc, hd_private::mainnet);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret ecs = tmp.secret();

    dev::bytesRef bytesRef(ecs.begin(), ecs.size());
    string priKey;
    dev::Secret secret(bytesRef);

    priKey = secret.hex();

    return priKey;
}

string EthSeriesCoinApi::getPriKeyFromBtSeed(BtSeed btSeed, string password, vector<ChildNumber> path, NetParams netparams)
{
    KeyCrypterScrypt kcs;

    vector<uint8_t> randomSalt = btSeed.getRandomSalt();

    uint32_t N = netparams.getN();
    uint32_t R = netparams.getR();
    uint32_t P = netparams.getP();

    if(netparams.getApiVersion() == 0x01) {
        N =32768;
        R = 8;
        P = 1;
    }

    vector<uint8_t> vecIv;
    bool res = kcs.deriveKey(password, &randomSalt[0], 8, N, R, P, vecIv);
    if(!res) {
        return "";
    }

    EncryptedData encSeedData = btSeed.getEncryptedSeed();
    vector<uint8_t> vEncSeed = encSeedData.getEncryptedBytes();

    uint8_t plainSeed[1024] = {0};

    int len = kcs.decrypt(&vEncSeed[0], vEncSeed.size(), plainSeed, encSeedData.getIV());
    if(len <= 0) {
        return "";
    }

    vector<uint8_t> vSeed(&plainSeed[0], &plainSeed[0] + len);

    data_chunk dc;

    dc = to_chunk(vSeed);

    string strPriv;
    if(netparams.getApiVersion() == 0x01) {
        strPriv = getPrivate(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    else
    {
        strPriv = getPrivateV2(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    return strPriv;
}

bool EthSeriesCoinApi::createSignTransaction(CreateSignTransactionParams* params, vector<ChildNumber> path, NetParams netparams, map<string, string>& resultMap)
{
    EthCreateSignTransactionParams* ethParams = (EthCreateSignTransactionParams*)params;
    string priKey = ethParams->getPriKey();

    ec_secret secret_ec;

    if(priKey.length() == 0)
    {
        string tmpPriKey = getPriKeyFromBtSeed(ethParams->getBtSeed(), ethParams->getPassword(),
                                               path, netparams);

        dev::bytes prkBytes = dev::fromUserHex(tmpPriKey);
        for(int i = 0; i < prkBytes.size(); i++) {
            secret_ec[i] = prkBytes[i];
        }
    }
    else //private key sign
    {
        //ec_private ecPri(priKey, address_version);
        dev::bytes prkBytes = dev::fromUserHex(priKey);
        for(int i = 0; i < prkBytes.size(); i++) {
            secret_ec[i] = prkBytes[i];
        }
    }


    dev::bytesConstRef bytesRef(secret_ec.begin(), secret_ec.size());
    dev::Secret secret(bytesRef);

    dev::KeyPair keyPair(secret);
    dev::Address address = keyPair.address();
    string strAddress = address.hex();

    dev::eth::TransactionSkeleton ske;
    ske.creation = false;

    string from = ethParams->getFrom();
    ske.from = dev::h160(dev::fromUserHex(from));

    string to = ethParams->getTo();
    ske.to = dev::h160(dev::fromUserHex(to));

    u256 value = ethParams->getValue();
    ske.value = value;

    u256 nonce = ethParams->getNonce();
    ske.nonce = nonce;


    u256 gas = ethParams->getGasLimit();
    ske.gas = gas;

    u256 gasPrice = ethParams->getGasPrice();
    ske.gasPrice = gasPrice;

    vector<uint8_t> data = ethParams->getData();
    if(data.size() > 0)
        ske.data = data;

    dev::eth::Transaction transaction(ske, secret);
    dev::bytes hex = transaction.rlp();
    int size = hex.size();
    string strHex = dev::toHex(hex);

    std::stringstream strIO;
    string strLen;
    strIO << size;
    strIO >> strLen;

    resultMap.insert(make_pair("length", strLen));
    resultMap.insert(make_pair("hex", strHex));

    return true;
}


bool EthSeriesCoinApi::validateAddress(const string& address)
{
    int len = address.length();

    if(len != 42 && len != 40) {
        return false;
    }

    if(!(len == 42 && address.find("0x") == 0)) {
        return false;
    }

    int istart = 0;
    if(len == 42) {
        istart = 2;
    }
    for(int i = istart; i < len  - istart; i++) {
        if(!hex(address[i])){
            return false;
        }
    }

    return true;
}